//
//						NeWA_Win32.cpp
//

#include "NativeWindow.h"
#include "Application.h"
#include "Misc.h"
#include "Disposal.h"
#include "WidgetElement.h"
#include "GraphicsBase.h"
#include "WindowImpl.h"
#include <assert.h>

namespace newa
{
	LRESULT CALLBACK newa_window_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

	class AppImpl
	{
	public:
		int run()
		{
			MSG msg;

			for(;;)
			{
				if(/*m_wait_mode*/ false)
				{
					if(!::GetMessage(&msg, 0, 0, 0))
					{
						break;
					}
					::TranslateMessage(&msg);
					::DispatchMessage(&msg);
				}
				else
				{
					if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
					{
						::TranslateMessage(&msg);
						if(msg.message == WM_QUIT)
						{
							break;
						}
						::DispatchMessage(&msg);
					}
					else
					{
						::Sleep( 1 ); // ???
						//on_idle();
					}
				}
			}
			return (int)msg.wParam;
		}

		void init(HINSTANCE hInstance, LPSTR lpszCmdLine, int nCmdShow)
		{
			m_windows_instance = hInstance;
			m_windows_cmd_show = nCmdShow;
		}

		HINSTANCE windowsInstance() const
		{
			return m_windows_instance;
		}

		int windowsCmdShow() const
		{
			return m_windows_cmd_show;
		}

	private:
		HINSTANCE m_windows_instance;
		int m_windows_cmd_show;
	};
}

/************************************************************************/
/*              ImplOf< NativeWindow >                                  */
/************************************************************************/

namespace Loki
{
	void ImplOf< newa::NativeWindow >::owner( newa::NativeWindow * win )
	{
		m_owner = win;
	}

	newa::NativeWindow * ImplOf< newa::NativeWindow >::owner() const
	{
		return m_owner;
	}

	void ImplOf< newa::NativeWindow >::create( int CreationFlags, newa::NativeWindow * parent )
	{
		// here we do create the window
		int wflags = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;

		if(!m_window_class_registered) {
			WNDCLASS wc;
			wc.lpszClassName = _T("NEWA_NATIVE_WINDOW_CLASS");
			wc.lpfnWndProc = newa::newa_window_proc;
			wc.style = wflags;
			wc.hInstance = newa::appInstance().getImpl()->windowsInstance();
			wc.hIcon = 0; //LoadIcon(0, IDI_APPLICATION);
			wc.hCursor = LoadCursor(0, IDC_ARROW);
			wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
			wc.lpszMenuName = 0; //L"YAWA_MENU";
			wc.cbClsExtra = 0;
			wc.cbWndExtra = 0;
			::RegisterClass(&wc);

			m_window_class_registered = true;
		}

		//WS_POPUP | WS_VISIBLE | WS_OVERLAPPED / *| WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX* /;

		//wflags |= WS_THICKFRAME | WS_MAXIMIZEBOX;

		m_hwnd = ::CreateWindowEx
			(
				WS_EX_APPWINDOW, 
				_T("NEWA_NATIVE_WINDOW_CLASS"),
				_T("-= NeWA rulezzzz =-"),
				CreationFlags,
				0,//top(),
				0,//left(),
				1,//width(),
				1,//height(),
				parent ? parent->getImpl()->hwnd() : 0,
				0,
				newa::appInstance().getImpl()->windowsInstance(),
				0
			);

		//trackMouseEvent();


		::SetWindowLongPtr(hwnd(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
		//::ShowWindow(hwnd(), newa::appInstance().getImpl()->windowsCmdShow());
	}

	void ImplOf< newa::NativeWindow >::destroy()
	{
		//::SetWindowLongPtr(hwnd(), GWLP_USERDATA, 0);
		::DestroyWindow( hwnd() );
	}

	newa::Rect ImplOf< newa::NativeWindow >::rect() const
	{
		RECT r;
		::GetWindowRect( m_hwnd, &r );
		return newa::Rect( r.left, r.top, r.right - r.left, r.bottom - r.top );
	}

	void ImplOf< newa::NativeWindow >::rect( const newa::Rect& r )
	{
		newa::Rect nr = m_rect;
		if( r.x >= 0 )
			nr.x = r.x;
		if( r.y >= 0 )
			nr.y = r.y;
		if( r.width >= 0 )
			nr.width = r.width;
		if( r.height >= 0 )
			nr.height = r.height;
		if( ::MoveWindow( m_hwnd, nr.x, nr.y, nr.width, nr.height, TRUE ) )
			m_rect = nr;
	}

	newa::Rect ImplOf< newa::NativeWindow >::clientRect() const
	{
		RECT r;
		::GetClientRect( m_hwnd, &r );
		return newa::Rect( r.left, r.top, r.right - r.left, r.bottom - r.top );
	}

	void ImplOf< newa::NativeWindow >::clientRect( const newa::Rect& )
	{
		// ???
	}

	void ImplOf< newa::NativeWindow >::invalidate()
	{
		::InvalidateRect( m_hwnd, 0, TRUE);
		//::UpdateWindow( m_hwnd );
	}

	void ImplOf< newa::NativeWindow >::flushPixBuf( const newa::PixBuf& pb )
	{
		BITMAPINFO bmp;
		bmp.bmiHeader.biSize   = sizeof(BITMAPINFOHEADER);
		bmp.bmiHeader.biWidth  = pb.rect().width;
		bmp.bmiHeader.biHeight = -pb.rect().height;
		bmp.bmiHeader.biPlanes = 1;
		bmp.bmiHeader.biBitCount = (unsigned short)newa::PixelFormat::BITS_PER_PIXEL;
		bmp.bmiHeader.biCompression = 0;
		bmp.bmiHeader.biSizeImage = pb.bufSize();
		bmp.bmiHeader.biXPelsPerMeter = 0;
		bmp.bmiHeader.biYPelsPerMeter = 0;
		bmp.bmiHeader.biClrUsed = 0;
		bmp.bmiHeader.biClrImportant = 0;

		newa::Rect r = pb.rect();
		int ret = ::SetDIBitsToDevice
			(
				hdc(),            // handle to device context
				r.x, // dvc_x,           // x-coordinate of upper-left corner of 
				r.y, // dvc_y,           // y-coordinate of upper-left corner of 
				r.width, //dvc_width,       // source rectangle width
				r.height, //dvc_height,      // source rectangle height
				0, //bmp_x,           // x-coordinate of lower-left corner of 
				0, //bmp_y,           // y-coordinate of lower-left corner of 
				0,               // first scan line in array
				r.height, //bmp_height,      // number of scan lines
				pb.buf(), //m_buf,           // address of array with DIB bits
				&bmp,           // address of structure with bitmap info.
				DIB_RGB_COLORS   // RGB or palette indexes
			);
	}

	void ImplOf< newa::NativeWindow >::invalidate( const newa::Rect& r )
	{
		RECT wr;
		wr.left = r.x;
		wr.top = r.y;
		wr.right = r.x + r.width;
		wr.bottom = r.y + r.height;
		::InvalidateRect( m_hwnd, &wr, TRUE);
		::UpdateWindow( m_hwnd );
	}

	void ImplOf< newa::NativeWindow >::captureMouse()
	{
		ImplOf< newa::NativeWindow > * t = this;
		HWND my = hwnd();
		HWND my_m = m_hwnd;
		HWND h = ::SetCapture( hwnd() );
		HWND j = ::GetCapture();
	}

	void ImplOf< newa::NativeWindow >::releaseMouse()
	{
		::ReleaseCapture();
	}

	void ImplOf< newa::NativeWindow >::setFocus()
	{
		::SetFocus( hwnd() );
	}

	HWND ImplOf< newa::NativeWindow >::hwnd() const
	{
		return m_hwnd;
	}

	void ImplOf< newa::NativeWindow >::hdc( HDC _hdc )
	{
		m_hdc = _hdc;
	}

	HDC ImplOf< newa::NativeWindow >::hdc() const
	{
		return m_hdc;
	}

	bool Loki::ImplOf< newa::NativeWindow >::m_window_class_registered = false;
}

namespace newa
{

	Application::Application()
		: m_impl(new AppImpl)
	{
	}

	int Application::run()
	{
		return m_impl->run();
	}

	static MouseState mouseState( WPARAM wParam )
	{
		unsigned State = 0;
		
		if ( wParam & MK_CONTROL )
			State |= MouseState::MS_CONTROL;
		if ( wParam & MK_SHIFT )
			State |= MouseState::MS_SHIFT;
		if ( wParam & MK_LBUTTON )
			State |= MouseState::MS_LBUTTON;
		if ( wParam & MK_MBUTTON )
			State |= MouseState::MS_MBUTTON;
		if ( wParam & MK_RBUTTON )
			State |= MouseState::MS_RBUTTON;

		return MouseState( State );
	}

	template < e_eventid ID > 
	static void dispatchMouseEvent
		( 
			WPARAM wParam, 
			LPARAM lParam, 
			WidgetElement &we
		)
	{
		Point p( LOWORD(lParam), HIWORD(lParam) );
		EventTraits< ID >::DispatcherType d( p );
		
		d.dispatch 
			( 
				we, 
				xloki::MakeTuple< MouseState, int, int >
					(
						mouseState( wParam ), 
						LOWORD(lParam), 
						HIWORD(lParam)
					)
			);
	}

	template < e_eventid ID > 
	static void dispatchMouseEventNoArgs
		( 
			WPARAM wParam, 
			LPARAM lParam, 
			WidgetElement &we
		)
	{
		Point p( LOWORD(lParam), HIWORD(lParam) );
		EventTraits< ID >::DispatcherType d( p );

		d.dispatch ( we, xloki::MakeTuple() );
	}

	template < e_eventid ID >
	static void dispatchKeybordEvent
		( 
			WPARAM wParam, 
			LPARAM lParam, 
			WidgetElement &we
		)
	{
		e_keyevent ke;

		switch( ID )
		{
		case EVCHAR:
			ke = keNONE;
			break;
		case EVKEYDOWN:
			ke = keDOWN;
			break;
		case EVKEYUP:
			ke = keUP;
			break;
		default:
			assert( !"Trying to dispatch keyboard event that doesn't exist" );
		}

		EventTraits< ID >::DispatcherType d;
		d.dispatch
			( 
				we, 
				xloki::MakeTuple
				(
					KeyInput( ke, int( wParam ), LOWORD( lParam ) )
				)
			);
	}

	LRESULT CALLBACK newa_window_proc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		LRESULT ret = 0;
		Loki::ImplOf<NativeWindow> * inwin = reinterpret_cast< Loki::ImplOf< NativeWindow > *>
			(::GetWindowLongPtr(hWnd, GWLP_USERDATA));
		int sz = sizeof(inwin);
		if(!inwin)
			return ::DefWindowProc(hWnd, msg, wParam, lParam);

		NativeWindow * nwin = inwin->owner();
		Disposal& dsp = nwin->owner();
		WidgetElement& we = dsp.linkedElem();

		switch(msg) 
		{
			//--------------------------------------------------------------------
		case WM_CREATE:
			break;

			//--------------------------------------------------------------------
		case WM_SIZE:
			EventTraits< EVRESIZE >::DispatcherType().dispatch( we, xloki::MakeTuple<int, int>( LOWORD(lParam), HIWORD(lParam) ) );
			break;
		case WM_MOVE:
			EventTraits< EVRESIZE >::DispatcherType().dispatch( we, xloki::MakeTuple<int, int>( LOWORD(lParam), HIWORD(lParam) ) );
			break;
			//--------------------------------------------------------------------
		case WM_ERASEBKGND:
			break;

			//--------------------------------------------------------------------
		case WM_LBUTTONDOWN:
			dispatchMouseEvent< EVLMSDOWN > ( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_LBUTTONUP:
			dispatchMouseEvent< EVLMSUP > ( wParam, lParam, we );
			dispatchMouseEventNoArgs< EVCLICK > ( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_RBUTTONDOWN:
			dispatchMouseEvent< EVRMSDOWN > ( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_RBUTTONUP:
			dispatchMouseEvent< EVRMSUP >( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_MOUSEHOVER:
			dispatchMouseEventNoArgs< EVMSENTER >( wParam, lParam, we );
			break;

		case WM_MOUSELEAVE:
			dispatchMouseEventNoArgs< EVMSLEAVE >( wParam, lParam, we );
			break;

		case WM_MOUSEMOVE:
			dispatchMouseEvent< EVMSMOVE >( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_SYSKEYDOWN:
		case WM_KEYDOWN:
			dispatchKeybordEvent< EVKEYDOWN >( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_SYSKEYUP:
		case WM_KEYUP:
			dispatchKeybordEvent< EVKEYUP >( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_CHAR:
		case WM_SYSCHAR:
			dispatchKeybordEvent< EVCHAR > ( wParam, lParam, we );
			break;

			//--------------------------------------------------------------------
		case WM_PAINT: 
			{
				PAINTSTRUCT ps;
				inwin->hdc(::BeginPaint( inwin->hwnd(), &ps ));
				EventTraits< EVPAINT >::DispatcherType( we.rect().size() ).dispatch( we, xloki::MakeTuple() );
				::EndPaint( inwin->hwnd(), &ps );
			}
			break;

			//--------------------------------------------------------------------
		case WM_COMMAND:
			break;

			//--------------------------------------------------------------------
		case WM_DESTROY:
			//::PostQuitMessage(0); ???
			break;

		case WM_TIMER:
			//nwin->callTimerHandler(wParam);
			break;
			//--------------------------------------------------------------------
		case WM_CAPTURECHANGED:
			break;
		default:
			ret = ::DefWindowProc(hWnd, msg, wParam, lParam);
			break;
		}
		return ret;
	}

	
	/************************************************************************/
	/*          NativeWindow                                                */
	/************************************************************************/

	NativeWindow::NativeWindow(Disposal& d, e_window_type type, NativeWindow * parent )
		: m_owner( &d )
	{
		m_impl->owner( this );
		int CreationFlags = 0;

		switch( type )
		{
		case NW_CHILD:
			CreationFlags = WS_CHILD | WS_VISIBLE;
			break;
		case NW_TOPLEVEL:
			CreationFlags = 0; //WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX |WS_THICKFRAME | WS_MAXIMIZEBOX;
			break;
		case NW_POPUP:
			CreationFlags = WS_POPUP | WS_VISIBLE;
			break;
		}
		
		m_impl->create( CreationFlags, parent );
		rect( Rect ( 0, 0 ) );
	}

	Rect NativeWindow::rect() const
	{
		return m_impl->rect();
	}

	void NativeWindow::rect( const Rect& r )
	{
		m_impl->rect( r );
	}

	Rect NativeWindow::clientRect() const
	{
		return m_impl->clientRect();
	}

	void NativeWindow::clientRect( const Rect& r )
	{
		m_impl->clientRect( r );
	}

	void NativeWindow::invalidate()
	{
		m_impl->invalidate();
	}

	void NativeWindow::invalidate( const Rect& r)
	{
		m_impl->invalidate( r );
	}

	void NativeWindow::flushPixBuf( const PixBuf& pb )
	{
		return m_impl->flushPixBuf( pb );
	}

	void NativeWindow::captureMouse()
	{
		m_impl->captureMouse();
	}

	void NativeWindow::releaseMouse()
	{
		m_impl->releaseMouse();
	}

	void NativeWindow::setFocus()
	{
		m_impl->setFocus();
	}

	NativeWindow::~NativeWindow()
	{
		m_impl->destroy();
	}
	
	
	/************************************************************************/
	/*           KeyInput                                                   */
	/************************************************************************/
	
	bool KeyInput::ctrl() const
	{
		return ::GetKeyState(VK_CONTROL) < 0;
	}

	bool KeyInput::alt() const
	{
		return ::GetKeyState(VK_MENU) < 0;
	}
	bool KeyInput::shift() const
	{
		return ::GetKeyState(VK_SHIFT) < 0;
	}
} // end of namespace newa

extern int newa_main();

int PASCAL WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
				   LPSTR lpszCmdLine,
				   int nCmdShow)
{
	newa::appInstance().getImpl()->init(hInstance, lpszCmdLine, nCmdShow);

	return newa_main();
}
